package com.doudou.mybatis.mate.core.interceptors;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * LogInterceptor
 *
 * @author jiangcs
 * @since 2022/4/4
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class LogInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        BoundSql boundSql = mappedStatement.getBoundSql(args[1]);
        Configuration configuration = mappedStatement.getConfiguration();

        Object result;
        long startTime = System.currentTimeMillis();
        try {
            result = invocation.proceed();
        } finally {
            long endTime = System.currentTimeMillis();

            String sql = getSql(boundSql, configuration);

            System.out.println("execute sql[" + sql + "], cost[" + (endTime - startTime) + "]ms");
        }
        return result;
    }

    private String getSql(BoundSql boundSql, Configuration configuration) {
        String sql = boundSql.getSql();
        sql = formatSql(sql);

        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            // 参数 为 基本类型 + String
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = replaceSql(sql, parameterObject);
            }
            // 参数 为 对象
            else {
                MetaObject metaObject = SystemMetaObject.forObject(parameterObject);
                // 存储：#{}
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                if (!parameterMappings.isEmpty()) {
                    for (ParameterMapping parameterMapping : parameterMappings) {
                        String property = parameterMapping.getProperty();
                        // 非动态属性名
                        if (metaObject.hasGetter(property)) {
                            sql = replaceSql(sql, metaObject.getValue(property));
                        }
                        // 动态属性名：属性名会追加前缀，比如，<foreach>
                        else if (boundSql.hasAdditionalParameter(property)) {
                            sql = replaceSql(sql, boundSql.getAdditionalParameter(property));
                        }
                    }
                }
            }
        }

        return sql;
    }

    private String formatSql(String sql) {
        return sql.replaceAll("[\\s\n]+", " ");
    }

    private String replaceSql(String sql, Object parameterObject) {
        String repStr;
        if (parameterObject instanceof String) {
            repStr = "'" + parameterObject + "'";
        } else if (parameterObject instanceof Date) {
            repStr = "'" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameterObject) + "'";
        } else {
            repStr = parameterObject.toString();
        }
        return sql.replaceFirst("\\?", repStr);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
